package taskmaster.util;

import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.util.Date;
import java.util.StringTokenizer;
import java.util.Hashtable;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import taskmaster.job.QueryParams;
import taskmaster.auth.User;
import taskmaster.auth.Login;

/**
 * SessionPersist.java
 *
 * Created on December 20, 2002, 5:06 PM
 *
 * @author  darrell
 */
public class SessionPersist {
	
	/** Creates a new instance of SessionPersist */
	public SessionPersist() {
		//
	}
	
	/* auto login cookie section starts */
	
	/** look for the cookie "CGI_TMS_LOGON", parse its data and use it to attempt a logon.
	 *@returns true if logon was successful, false otherwise
	 */
	public static boolean tryAutoLogin(User user, HttpServletRequest request, HttpServletResponse response) throws Exception {
		Cookie persistentLogon = findCookie("CGI_TMS_LOGON", request);
		if (persistentLogon == null)
			return false;
		
		String bytecode = (String)deserializeFromString(persistentLogon.getValue());
		
		String[] logonData = new String[4];
		StringTokenizer st = new StringTokenizer(bytecode, "&");
		
		for (int i=0; st.hasMoreTokens() && i<3; i++)
			logonData[i] = st.nextToken();
		
		// take care of the special case when the password itself contains a '&', even though passwords should have any
		if (st.hasMoreTokens())
			logonData[3] = st.nextToken();
		while (st.hasMoreTokens())
			logonData[3] += "&"+ st.nextToken();
		// take care of the special case when the password itself contains a '&' even though passwords should have any
		
		for (int i=0; i<logonData.length; i++)
			if (logonData[i] == null)
				return false;
		
		Login login = new Login();
//		boolean autoLogin = login.login(user, logonData[0], logonData[3], logonData[1], logonData[2], "yes", request, response);
		boolean autoLogin = false;

		if (autoLogin)
			System.err.println(new Date().getTime() +" -auto-logged in user "+ user.getUsername() +" using cookie CGI_TMS_LOGON");
		else
			System.err.println(new Date().getTime() +" -could not find CGI_TMS_LOGON cookie; cannot auto-login user");
		return autoLogin;
	}
	
	public static void deletePersistentLogonCookie(HttpServletRequest request, HttpServletResponse response) throws Exception {
		try {
			Cookie persistentLogon = findCookie("CGI_TMS_LOGON", request);
			if (persistentLogon == null)
				return;
			persistentLogon.setValue(serializeToString("deleted"));
//			persistentLogon.setMaxAge(0);
			response.addCookie(persistentLogon);
			
			System.err.println(new Date().getTime() +" -deleted CGI_TMS_LOGON cookie on users machine");
		} catch (Exception e) {
			String expl = "Exception taskmaster.auth.Login:deletePersistentLogonCookie "+ e;
			System.err.println(expl);
			throw new Exception (expl);
		}
	}
	
	public static void setPersistentLogonCookie(User user, String host, String database, HttpServletResponse response) throws Exception {
		try {
			// put password at end of string so that it can be detected if it contains the delimeter ('&') itself.
			StringBuffer cookValue = new StringBuffer();
			cookValue.append(user.getUsername()).append("&");
			cookValue.append(host).append("&").append(database).append("&").append(user.getPassword());
			
			String bytecode = serializeToString(cookValue.toString());
			
			Cookie persistentLogon = new Cookie("CGI_TMS_LOGON", bytecode);
			persistentLogon.setMaxAge(-1);
			persistentLogon.setPath("/");
			response.addCookie(persistentLogon);
			
			System.err.println(new Date().getTime() +" -CGI_TMS_LOGON cookie was sent to the users machine");
		} catch (Exception e) {
			String expl = "Exception taskmaster.auth.Login:setPersistentLogon "+ e;
			System.err.println(expl);
			throw new Exception (expl);
		}
	}
	
	
	
	/* QueryParams cookie section starts */
	
	
	public static void setQueryParamCookie(QueryParams limiters, HttpServletResponse response) throws Exception {
		String serialized = serializeToString(limiters);
		Cookie query = new Cookie("CGI_TMS_QUERY", serialized);
		query.setMaxAge(-1);
		query.setPath("/");
		response.addCookie(query);
		
		System.err.println(new Date().getTime() +" -QueryParams cookie sent to client");
	}
	
	public static QueryParams getQueryParamsFromCookie(HttpServletRequest request) throws Exception {
		Cookie storedParams = findCookie("CGI_TMS_QUERY", request);
		if (storedParams == null)
			return null;
		
		QueryParams lastLimiters = (QueryParams)deserializeFromString(storedParams.getValue());
		return lastLimiters;
	}
	
	
	/* hashtable as cookie starts */
	
	public static void setHashCookie(Hashtable hash, HttpServletResponse response) throws Exception {
		String toStore = serializeToString(hash);
		Cookie hashCookie = new Cookie("CGI_TMS_TASKDET", toStore);
		hashCookie.setMaxAge(-1);
		hashCookie.setPath("/");
		response.addCookie(hashCookie);
		
		System.err.println(new Date().getTime() +" -detail view tasks cookie sent to client");
	}
	
	public static Hashtable getHashCookie(HttpServletRequest request) throws Exception {
		Cookie storedHash = findCookie("CGI_TMS_TASKDET", request);
		System.err.println(new Date().getTime() +" -"+ storedHash);
		if (storedHash == null)
			return null;
		
		Hashtable hash = (Hashtable)deserializeFromString(storedHash.getValue());
		System.err.println(new Date().getTime() +" -detail view tasks cookie retrieved from client");
		return hash;
	}
	
	
	
	/* shared cookie parts start */
	
	/** simple internal method that looks through all the cookies and returns the one matching the param 'name' */
	private static Cookie findCookie(String name, HttpServletRequest request) throws Exception {
		Cookie[] allCookies = request.getCookies();
		if (allCookies != null)
			for (int i=0; i<allCookies.length; i++)
				if (name.equals(allCookies[i].getName()))
					return allCookies[i];
		return null;
	}
	
	
	/* encoding / decoding starts */
	
	
	public static String serializeToString(Object o) throws IOException, Exception {
		byte[] bytes = serialize(o);
		return encode(bytes);
	}
	public static Object deserializeFromString(String s) throws IOException, Exception {
		byte[] bytes = decode(s);
		return deserialize(bytes);
	}
	
	private static byte[] serialize(Object o) throws IOException {
		ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(byteStream);
		oos.writeObject(o);
		return byteStream.toByteArray();
	}
	
	private static Object deserialize(byte[] byteArray) throws IOException, ClassNotFoundException {
		ByteArrayInputStream byteStream = new ByteArrayInputStream(byteArray);
		ObjectInputStream oos = new ObjectInputStream(byteStream);
		return oos.readObject();
	}
	
	/**
	 *  Encode some data and return a String.
	 */
	private final static String encode(byte[] d) {
		if (d == null) return null;
		byte data[] = new byte[d.length+2];
		System.arraycopy(d, 0, data, 0, d.length);
		byte dest[] = new byte[(data.length/3)*4];
		// 3-byte to 4-byte conversion
		for (int sidx = 0, didx=0; sidx < d.length; sidx += 3, didx += 4) {
			dest[didx]   = (byte) ((data[sidx] >>> 2) & 077);
			dest[didx+1] = (byte) ((data[sidx+1] >>> 4) & 017 | (data[sidx] << 4) & 077);
			dest[didx+2] = (byte) ((data[sidx+2] >>> 6) & 003 | (data[sidx+1] << 2) & 077);
			dest[didx+3] = (byte) (data[sidx+2] & 077);
		}
		// 0-63 to ascii printable conversion
		for (int idx = 0; idx <dest.length; idx++) {
			if (dest[idx] < 26)     dest[idx] = (byte)(dest[idx] + 'A');
			else if (dest[idx] < 52)  dest[idx] = (byte)(dest[idx] + 'a' - 26);
			else if (dest[idx] < 62)  dest[idx] = (byte)(dest[idx] + '0' - 52);
			else if (dest[idx] < 63)  dest[idx] = (byte)'+';
			else dest[idx] = (byte)'/';
		}
		// add padding
		for (int idx = dest.length-1; idx > (d.length*4)/3; idx--) {
			dest[idx] = (byte)'=';
		}
		return new String(dest);
	}
	
	/**
	 *  Decode data and return bytes.
	 */
	private final static byte[] decode(String str) {
		if (str == null)  return  null;
		byte data[] = new byte[str.length()];
		str.getBytes(0, str.length(), data, 0);
		return decode(data);
	}
	
	/**
	 *  Decode data and return bytes.  Assumes that the data passed
	 *  in is ASCII text.
	 */
	private final static byte[] decode(byte[] data) {
		int tail = data.length;
		while (data[tail-1] == '=')  tail--;
		byte dest[] = new byte[tail - data.length/4];
		
		// ascii printable to 0-63 conversion
		for (int idx = 0; idx <data.length; idx++) {
			if (data[idx] == '=')    data[idx] = 0;
			else if (data[idx] == '/') data[idx] = 63;
			else if (data[idx] == '+') data[idx] = 62;
			else if (data[idx] >= '0'  &&  data[idx] <= '9')
				data[idx] = (byte)(data[idx] - ('0' - 52));
			else if (data[idx] >= 'a'  &&  data[idx] <= 'z')
				data[idx] = (byte)(data[idx] - ('a' - 26));
			else if (data[idx] >= 'A'  &&  data[idx] <= 'Z')
				data[idx] = (byte)(data[idx] - 'A');
		}
		
		// 4-byte to 3-byte conversion
		int sidx, didx;
		for (sidx = 0, didx=0; didx < dest.length-2; sidx += 4, didx += 3) {
			dest[didx]   = (byte) ( ((data[sidx] << 2) & 255) | ((data[sidx+1] >>> 4) & 3) );
			dest[didx+1] = (byte) ( ((data[sidx+1] << 4) & 255) | ((data[sidx+2] >>> 2) & 017) );
			dest[didx+2] = (byte) ( ((data[sidx+2] << 6) & 255) | (data[sidx+3] & 077) );
		}
		if (didx < dest.length) {
			dest[didx]   = (byte) ( ((data[sidx] << 2) & 255) | ((data[sidx+1] >>> 4) & 3) );
		}
		if (++didx < dest.length) {
			dest[didx]   = (byte) ( ((data[sidx+1] << 4) & 255) | ((data[sidx+2] >>> 2) & 017) );
		}
		return dest;
	}
}
